import { WebsocketClient, isWsAggTradeFormatted, isWsFormatted24hrTicker, isWsFormattedKline, WsMessageKlineFormatted, WsMessageTradeFormatted } from "binance";
import EventEmitter from "events";
import { outLogger, errLogger, debugLogger } from "../common/logger";
import { Period } from "../trader";

const { SocksProxyAgent } = require('socks-proxy-agent');

export class BinanceWebSocket {
  /** @see https://github.com/tiagosiebler/binance/blob/master/src/websocket-client.ts */
  wsClient: WebsocketClient;

  private eventEmitter = new EventEmitter();

  constructor() {
    this.init()
  }
  init() {
    const agent = new SocksProxyAgent(process.env.socks_proxy);
    outLogger.info('socks_proxy', process.env.socks_proxy)
    const wsClient = new WebsocketClient({
      beautify: true,
      wsOptions: {
        agent: agent
      }
    }, {
      silly: (...params) => {
        // console.info('ws silly', ...params);
      },
      debug: (...params) => {
        outLogger.info('ws debug', ...params);
      },
      notice: (...params) => {
        outLogger.info('ws notice', ...params);
      },
      info: (...params) => {
        // outLogger.info('ws info', ...params);
      },
      warning: (...params) => {
        outLogger.warn('ws warning', ...params);
      },
      error: (err) => {
        errLogger.error('ws error', err);
      }
    });
    this.wsClient = wsClient;
    wsClient.on('message', (data) => {

      if (!Array.isArray(data) && (data as any).e === 'depthUpdate') {
        this.eventEmitter.emit((data as any).wsKey, data);
      }
    });
    wsClient.on('formattedMessage', (data) => {
      if (isWsFormattedKline(data)) {
        this.eventEmitter.emit(data.wsKey, data.kline);
        return;
      }
      if (isWsFormatted24hrTicker(data)) {
        console.log('24hrTicker received ', data);
        return;
      }
      if (isWsAggTradeFormatted(data) && data.symbol) {
        this.eventEmitter.emit(data.wsKey, data);
        return;
      }
    });

    wsClient.on('reply', (data) => {
      errLogger.info('log reply: ', JSON.stringify(data, null, 2));
    });
    wsClient.on('reconnecting', (data) => {
      debugLogger(`ws automatically reconnecting.... ${data?.wsKey}`, true);
    });
    wsClient.on('error', (event) => {
      console.error('wsClient error', event.wsKey, event.error.message)
    })
  }
  subscribeSpotKline(symbol: string, period: Period, callback: (event: WsMessageKlineFormatted['kline']) => void) {
    const key = `spot_kline_${symbol.toLowerCase()}_${period}`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeSpotKline(symbol, period);
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }

  subscribeSpotDepth(symbol: string, callback: (event: {s: string; E: number, b: [string, string][], a: [string, string][]}) => void) {
    const key = `spot_diffBookDepth_${symbol.toLowerCase()}_1000`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeSpotDiffBookDepth(symbol);
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }

  subscribeSpotAggregateTrades(symbol: string, callback: (event: WsMessageTradeFormatted) => void) {
    const key = `spot_aggTrade_${symbol.toLowerCase()}_`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeSpotAggregateTrades(symbol);
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }

  subscribeUSDMKlines(symbol: string, period: Period, callback: (event: WsMessageKlineFormatted['kline']) => void) {
    const key = `usdm_kline_${symbol.toLowerCase()}_${period}`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeKlines(symbol, period, 'usdm');
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }
  subscribeUSDMDepth(symbol: string, callback: (event: {s: string; E: number, b: [string, string][], a: [string, string][]}) => void) {
    const key = `usdm_diffBookDepth_${symbol.toLowerCase()}_500`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeDiffBookDepth(symbol, 500, 'usdm');
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }

  subscribeDeliveryDepth(symbol: string, callback: (event: {s: string; E: number, b: [string, string][], a: [string, string][]}) => void) {
    const key = `coinm_diffBookDepth_${symbol.toLowerCase()}_500`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeDiffBookDepth(symbol, 500, 'coinm');
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }
  subscribeDeliveryKlines(symbol: string, period: Period, callback: (event: WsMessageKlineFormatted['kline']) => void) {
    const key = `coinm_kline_${symbol.toLowerCase()}_${period}`;
    this.eventEmitter.on(key, callback);
    const ws = this.wsClient.subscribeKlines(symbol, period, 'coinm');
    return () => {
      this.wsClient.closeWs(ws);
      this.eventEmitter.removeListener(key, callback);
    }
  }
}

// const binanceWebSocket = new BinanceWebSocket();
// binanceWebSocket.subscribeDeliveryDepth("ETHUSD_230630", (data) => {
//   console.log(data)
// });